/*
 * Copyright 2012 Evernote Corporation
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.evernote.client.android;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.Window;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
import com.evernote.androidsdk.R;
import com.evernote.client.oauth.EvernoteAuthToken;
import com.evernote.client.oauth.YinxiangApi;
import com.evernote.edam.userstore.BootstrapInfo;
import com.evernote.edam.userstore.BootstrapProfile;
import org.scribe.builder.ServiceBuilder;
import org.scribe.builder.api.EvernoteApi;
import org.scribe.model.Token;
import org.scribe.model.Verifier;
import org.scribe.oauth.OAuthService;

import java.util.ArrayList;

/**
 * An Android Activity for authenticating to Evernote using OAuth. Third parties
 * should not need to use this class directly.
 * 
 * 
 * class created by @tylersmithnet
 */
public class EvernoteOAuthActivity extends ActionBarActivity {
	private static final String LOGTAG = "EvernoteOAuthActivity";

	static final String EXTRA_EVERNOTE_SERVICE = "EVERNOTE_HOST";
	static final String EXTRA_CONSUMER_KEY = "CONSUMER_KEY";
	static final String EXTRA_CONSUMER_SECRET = "CONSUMER_SECRET";
	static final String EXTRA_REQUEST_TOKEN = "REQUEST_TOKEN";
	static final String EXTRA_REQUEST_TOKEN_SECRET = "REQUEST_TOKEN_SECRET";
	static final String EXTRA_BOOTSTRAP_SELECTED_PROFILE_POS = "BOOTSTRAP_SELECTED_PROFILE_POS";
	static final String EXTRA_BOOTSTRAP_SELECTED_PROFILE = "BOOTSTRAP_SELECTED_PROFILE";
	static final String EXTRA_BOOTSTRAP_SELECTED_PROFILES = "BOOTSTRAP_SELECTED_PROFILES";

	private EvernoteSession.EvernoteService mEvernoteService = null;

	private BootstrapProfile mSelectedBootstrapProfile;
	private int mSelectedBootstrapProfilePos = 0;
	private ArrayList<BootstrapProfile> mBootstrapProfiles = new ArrayList<BootstrapProfile>();

	private String mConsumerKey = null;
	private String mConsumerSecret = null;
	private String mRequestToken = null;
	private String mRequestTokenSecret = null;

	private final int DIALOG_PROGRESS = 101;

	private Activity mActivity;

	private WebView mWebView;

	private AsyncTask mBeginAuthSyncTask = null;
	private AsyncTask mCompleteAuthSyncTask = null;

	private Menu menu;

	/**
	 * Overrides the callback URL and authenticate
	 */
	private WebViewClient mWebViewClient = new WebViewClient() {

		@Override
		public boolean shouldOverrideUrlLoading(WebView view, String url) {
			Uri uri = Uri.parse(url);
			if (uri.getScheme().equals(getCallbackScheme())) {
				if (mCompleteAuthSyncTask == null) {
					mCompleteAuthSyncTask = new CompleteAuthAsyncTask()
							.execute(uri);
				}
				return true;
			}
			return super.shouldOverrideUrlLoading(view, url);
		}
	};

	/**
	 * Allows for showing progress
	 */
	private WebChromeClient mWebChromeClient = new WebChromeClient() {
		@Override
		public void onProgressChanged(WebView view, int newProgress) {
			super.onProgressChanged(view, newProgress);
			mActivity.setProgress(newProgress * 1000);
		}
	};

	@SuppressLint("SetJavaScriptEnabled")
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		// Show web loading progress
		getWindow().requestFeature(Window.FEATURE_PROGRESS);

		setContentView(R.layout.esdk__webview);
		getSupportActionBar().setDisplayHomeAsUpEnabled(true);
		getSupportActionBar().setDisplayUseLogoEnabled(false);
		getSupportActionBar().setDisplayShowTitleEnabled(true);
		mActivity = this;

		mWebView = (WebView) findViewById(R.id.esdk__webview);
		mWebView.setWebViewClient(mWebViewClient);
		mWebView.setWebChromeClient(mWebChromeClient);
		mWebView.getSettings().setJavaScriptEnabled(true);

		if (savedInstanceState != null) {
			mEvernoteService = savedInstanceState
					.getParcelable(EXTRA_EVERNOTE_SERVICE);
			mConsumerKey = savedInstanceState.getString(EXTRA_CONSUMER_KEY);
			mConsumerSecret = savedInstanceState
					.getString(EXTRA_CONSUMER_SECRET);
			mRequestToken = savedInstanceState.getString(EXTRA_REQUEST_TOKEN);
			mRequestTokenSecret = savedInstanceState
					.getString(EXTRA_REQUEST_TOKEN_SECRET);
			mSelectedBootstrapProfile = (BootstrapProfile) savedInstanceState
					.getSerializable(EXTRA_BOOTSTRAP_SELECTED_PROFILE);
			mSelectedBootstrapProfilePos = savedInstanceState
					.getInt(EXTRA_BOOTSTRAP_SELECTED_PROFILE_POS);
			mBootstrapProfiles = (ArrayList<BootstrapProfile>) savedInstanceState
					.getSerializable(EXTRA_BOOTSTRAP_SELECTED_PROFILES);
			mWebView.restoreState(savedInstanceState);

		} else {
			Intent intent = getIntent();
			mEvernoteService = intent
					.getParcelableExtra(EXTRA_EVERNOTE_SERVICE);
			mConsumerKey = intent.getStringExtra(EXTRA_CONSUMER_KEY);
			mConsumerSecret = intent.getStringExtra(EXTRA_CONSUMER_SECRET);
		}
	}

	@Override
	protected void onResume() {
		super.onResume();

		if (TextUtils.isEmpty(mConsumerKey)
				|| TextUtils.isEmpty(mConsumerSecret)) {
			exit(false);
			return;
		}

		if (mSelectedBootstrapProfile == null) {
			mBeginAuthSyncTask = new BootstrapAsyncTask().execute();
		}
	}

	/**
	 * Not needed because of conficChanges, but leaving in case developer does
	 * not add to manifest
	 * 
	 * @param outState
	 */
	@Override
	protected void onSaveInstanceState(Bundle outState) {
		outState.putParcelable(EXTRA_EVERNOTE_SERVICE, mEvernoteService);
		outState.putString(EXTRA_CONSUMER_KEY, mConsumerKey);
		outState.putString(EXTRA_CONSUMER_SECRET, mConsumerSecret);
		outState.putString(EXTRA_REQUEST_TOKEN, mRequestToken);
		outState.putString(EXTRA_REQUEST_TOKEN_SECRET, mRequestTokenSecret);
		outState.putSerializable(EXTRA_BOOTSTRAP_SELECTED_PROFILE,
				mSelectedBootstrapProfile);
		outState.putInt(EXTRA_BOOTSTRAP_SELECTED_PROFILE_POS,
				mSelectedBootstrapProfilePos);
		outState.putSerializable(EXTRA_BOOTSTRAP_SELECTED_PROFILES,
				mBootstrapProfiles);
		mWebView.saveState(outState);

		super.onSaveInstanceState(outState);
	}

	@Override
	protected Dialog onCreateDialog(int id) {
		switch (id) {
		case DIALOG_PROGRESS:
			return new ProgressDialog(EvernoteOAuthActivity.this);
		}
		// TODO onCreateDialog(int) is deprecated
		return super.onCreateDialog(id);
	}

	@Override
	protected void onPrepareDialog(int id, Dialog dialog) {
		switch (id) {
		case DIALOG_PROGRESS:
			((ProgressDialog) dialog).setIndeterminate(true);
			dialog.setCancelable(false);
			((ProgressDialog) dialog)
					.setMessage(getString(R.string.esdk__loading));
		}
	}

	/**
	 * Specifies a URL scheme that uniquely identifies callbacks to this
	 * application after a user authorizes access to their Evernote account in
	 * our WebView.
	 */
	private String getCallbackScheme() {
		return "en-oauth";
	}

	/**
	 * Create a Scribe OAuthService object that can be used to perform OAuth
	 * authentication with the appropriate Evernote service.
	 */
	@SuppressWarnings("unchecked")
	private OAuthService createService() {
		OAuthService builder = null;
		@SuppressWarnings("rawtypes")
		Class apiClass = null;
		String host = mSelectedBootstrapProfile.getSettings().getServiceHost();

		if (host != null && !host.startsWith("http")) {
			host = "https://" + host;
		}

		if (host.equals(EvernoteSession.HOST_SANDBOX)) {
			apiClass = EvernoteApi.Sandbox.class;
		} else if (host.equals(EvernoteSession.HOST_PRODUCTION)) {
			apiClass = EvernoteApi.class;
		} else if (host.equals(EvernoteSession.HOST_CHINA)) {
			apiClass = YinxiangApi.class;
		} else {
			throw new IllegalArgumentException("Unsupported Evernote host: "
					+ host);
		}
		builder = new ServiceBuilder().provider(apiClass).apiKey(mConsumerKey)
				.apiSecret(mConsumerSecret)
				.callback(getCallbackScheme() + "://callback").build();

		return builder;
	}

	/**
	 * Exit the activity and display a toast message.
	 * 
	 * @param success
	 *            Whether the OAuth process completed successfully.
	 */
	private void exit(final boolean success) {
		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				Toast.makeText(
						mActivity,
						success ? R.string.esdk__evernote_login_successful
								: R.string.esdk__evernote_login_failed,
						Toast.LENGTH_LONG).show();
				setResult(success ? RESULT_OK : RESULT_CANCELED);
				finish();
			}
		});
	}

	/**
	 * On honeycomb and above this will create an actionbar with the item to
	 * switch services Below honeycomb it will create the options menu bound to
	 * a hardware key
	 * 
	 * @param menu
	 * @return
	 */
	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		MenuInflater inflater = getMenuInflater();
		inflater.inflate(R.menu.esdk__oauth, menu);
		this.menu = menu;
		return true;
	}

	/**
	 * On Honeycomb and above this is called when we invalidate, this happens
	 * when the {@link ArrayList} of {@link BootstrapProfile} are updated.
	 * 
	 * Below Honeycomb this is called when the user presses the menu button.
	 * 
	 * This detects the number of bootstrap items and sets the UI element
	 * appropriately.
	 * 
	 * @param menu
	 * @return
	 */
	@Override
	public boolean onPrepareOptionsMenu(Menu menu) {
		MenuItem itemSwitchService = menu.findItem(R.id.esdk__switch_service);

		if (mBootstrapProfiles != null && mBootstrapProfiles.size() > 1) {
			itemSwitchService.setVisible(true);
			if (BootstrapManager.CHINA_PROFILE.equals(mSelectedBootstrapProfile
					.getName())) {
				itemSwitchService
						.setTitle(BootstrapManager.DISPLAY_EVERNOTE_INTL);
			} else {
				itemSwitchService.setTitle(BootstrapManager.DISPLAY_YXBIJI);
			}

		} else {
			itemSwitchService.setVisible(true);
		}

		return true;
	}

	/**
	 * This will select the next {@link BootstrapProfile} in
	 * {@link #mBootstrapProfiles} and start a new webview load request.
	 * 
	 * @param item
	 * @return
	 */
	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		if (item.getItemId() == R.id.esdk__switch_service) {
			if ((mBeginAuthSyncTask == null || mBeginAuthSyncTask.getStatus() != AsyncTask.Status.RUNNING)
					&& (mSelectedBootstrapProfile != null && mBootstrapProfiles != null)) {

				mSelectedBootstrapProfilePos = (mSelectedBootstrapProfilePos + 1)
						% mBootstrapProfiles.size();
				if (BootstrapManager.CHINA_PROFILE
						.equals(mSelectedBootstrapProfile.getName())) {
					item.setTitle(BootstrapManager.DISPLAY_YXBIJI);
				} else {
					item.setTitle(BootstrapManager.DISPLAY_EVERNOTE_INTL);

				}
				mBootstrapProfiles = null;
				mSelectedBootstrapProfile = null;
				mBeginAuthSyncTask = new BootstrapAsyncTask().execute();

			}

		} else if (item.getItemId() == android.R.id.home) {
			exit(false);
		}
		return true;
	}

	/**
	 * Get a request token from the Evernote service and send the user to our
	 * WebView to authorize access.
	 */
	private class BootstrapAsyncTask extends AsyncTask<Void, Void, String> {

		@Override
		protected void onPreExecute() {
			// TODO deprecated
			showDialog(DIALOG_PROGRESS);
		}

		@Override
		protected String doInBackground(Void... params) {
			String url = null;
			try {

				EvernoteSession session = EvernoteSession.getOpenSession();
				if (session != null) {
					// Network request
					BootstrapManager.BootstrapInfoWrapper infoWrapper = session
							.getBootstrapSession().getBootstrapInfo();

					if (infoWrapper != null) {
						BootstrapInfo info = infoWrapper.getBootstrapInfo();
						if (info != null) {
							mBootstrapProfiles = (ArrayList<BootstrapProfile>) info
									.getProfiles();
							if (mBootstrapProfiles != null
									&& mBootstrapProfiles.size() > 0
									&& mSelectedBootstrapProfilePos < mBootstrapProfiles
											.size()) {

								mSelectedBootstrapProfile = mBootstrapProfiles
										.get(mSelectedBootstrapProfilePos);
							}
						}
					}
				}

				if (mSelectedBootstrapProfile == null
						|| TextUtils.isEmpty(mSelectedBootstrapProfile
								.getSettings().getServiceHost())) {
					Log.d(LOGTAG, "Bootstrap did not return a valid host");
					return null;
				}

				OAuthService service = createService();

				Log.i(LOGTAG, "Retrieving OAuth request token...");
				Token reqToken = service.getRequestToken();
				mRequestToken = reqToken.getToken();
				mRequestTokenSecret = reqToken.getSecret();

				Log.i(LOGTAG, "Redirecting user for authorization...");
				url = service.getAuthorizationUrl(reqToken);
			} catch (BootstrapManager.ClientUnsupportedException cue) {

				return null;
			} catch (Exception ex) {
				Log.e(LOGTAG, "Failed to obtain OAuth request token", ex);
			}
			return url;
		}

		/**
		 * Open a WebView to allow the user to authorize access to their
		 * account.
		 * 
		 * @param url
		 *            The URL of the OAuth authorization web page.
		 */
		@SuppressLint("NewApi")
		@Override
		protected void onPostExecute(String url) {
			// TODO deprecated
			removeDialog(DIALOG_PROGRESS);
			if (!TextUtils.isEmpty(url)) {
				mWebView.loadUrl(url);

				if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
					invalidateOptionsMenu();
				}
				onPrepareOptionsMenu(menu);
			} else {
				exit(false);
			}
		}
	}

	/**
	 * An AsyncTask to complete the OAuth process after successful user
	 * authorization.
	 */
	private class CompleteAuthAsyncTask extends
			AsyncTask<Uri, Void, EvernoteAuthToken> {

		@Override
		protected void onPreExecute() {
			// TODO deprecated
			showDialog(DIALOG_PROGRESS);
		}

		@Override
		protected EvernoteAuthToken doInBackground(Uri... uris) {
			EvernoteAuthToken authToken = null;
			if (uris == null || uris.length == 0) {
				return null;
			}
			Uri uri = uris[0];

			if (!TextUtils.isEmpty(mRequestToken)) {
				OAuthService service = createService();
				String verifierString = uri.getQueryParameter("oauth_verifier");
				if (TextUtils.isEmpty(verifierString)) {
					Log.i(LOGTAG, "User did not authorize access");
				} else {
					Verifier verifier = new Verifier(verifierString);
					Log.i(LOGTAG, "Retrieving OAuth access token...");
					try {
						Token reqToken = new Token(mRequestToken,
								mRequestTokenSecret);
						authToken = new EvernoteAuthToken(
								service.getAccessToken(reqToken, verifier));
					} catch (Exception ex) {
						Log.e(LOGTAG, "Failed to obtain OAuth access token", ex);
					}
				}
			} else {
				Log.d(LOGTAG,
						"Unable to retrieve OAuth access token, no request token");
			}

			return authToken;
		}

		/**
		 * Save the authentication information resulting from a successful OAuth
		 * authorization and complete the activity.
		 */

		@Override
		protected void onPostExecute(EvernoteAuthToken authToken) {
			// TODO deprecated
			removeDialog(DIALOG_PROGRESS);
			if (EvernoteSession.getOpenSession() == null) {
				exit(false);
				return;
			}

			exit(EvernoteSession.getOpenSession().persistAuthenticationToken(
					getApplicationContext(), authToken,
					mSelectedBootstrapProfile.getSettings().getServiceHost()));
		}
	}

}
